package org.lightrpc.client;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import org.lightrpc.RemoteCallFailedException;
import org.lightrpc.RemoteCallUrl;
import org.lightrpc.Response;
import org.lightrpc.RpcError;
import org.lightrpc.ServiceNameResolver;
import org.lightrpc.format.DataSerializer;
import org.lightrpc.format.json.GsonDataSerializer;
import org.lightrpc.server.http.HttpServiceNameResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This method actually handles the calling of the correct remote methods.
 * 
 * @author miku
 * 
 */
public class RemoteFacade implements InvocationHandler {
	private static final Logger logger = LoggerFactory
			.getLogger(RemoteFacade.class);

	private final String url;
	private final String path;
	private final int port;
	private final ServiceNameResolver serviceNameResolver;
	private RemoteClient remoteClient;
	private final DataSerializer dataSerializer;

	public RemoteFacade(String url, int port, String path) {
		this.url = url;
		this.path = path;
		this.port = port;
		// FIXME:again, this should be done differently
		serviceNameResolver = new HttpServiceNameResolver();
		remoteClient = new HttpRemoteClient();
		dataSerializer = new GsonDataSerializer();
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO:maybe this logics should not be here?
		RemoteCallUrl urlModel = new RemoteCallUrl(url, port, path);
		String servicePath = serviceNameResolver.resolveRemoteCall(method
				.getDeclaringClass(), method, args);
		// TODO add more specific error message
		logger.debug("servicePath: " + servicePath);
		urlModel.setServicePath(servicePath);
		Response resp = remoteClient.callService(urlModel);
		Object returnObject;
		// try to serialize the actual return type object if request was a
		// success
		if (resp.getStatus().equals(Response.RPC_STATUS_SUCCESS)) {
			logger.debug("resp: " + resp.getData() + " method: "
							+ method.getReturnType());
			returnObject = dataSerializer.deserialize(resp, method
					.getReturnType());
			logger.debug("returnObject: " + returnObject);
		} else if (resp.getStatus().equals(Response.RPC_STATUS_FAILURE)) {
			// interface provides the error
			RpcError error = dataSerializer.deserialize(resp, RpcError.class);
			for (Class<?> exception : method.getExceptionTypes()) {
				if (error.getErrorName().equalsIgnoreCase(
						exception.getSimpleName())) {
					Class<?>[] params = new Class[] { String.class };
					Throwable t;
					try {
						// try to allocate a string parameter constructor
						Constructor<?> constructor = Class.forName(
								exception.getName()).getConstructor(params);
						t = (Throwable) constructor.newInstance(error
								.getErrorMessage());
					} catch (Exception e) {// if no use default constructor
						t = (Throwable) Class.forName(exception.getName())
								.newInstance();
					}
					// TODO: adding causes recursively is still missing
					throw t;
				}
			}
			// if the interface cannot provide the throwable class, well
			// just throw a remoteexception
			throw new RemoteCallFailedException(error);
		} else {
			throw new RemoteCallFailedException("Unexcpected error occurred");
		}

		return returnObject;
	}

}
